Skip to content

Tomcat 中为什么要使用自定义类加载器

Tomcat 是一个广泛使用的 Java Servlet 容器,它采用了自定义类加载器架构而不是简单使用 Java 默认的类加载机制。这种设计有着深思熟虑的原因和明显的优势。

Tomcat 的类加载器层次结构

Tomcat 使用了一个层次化的类加载器结构:

  1. Bootstrap 类加载器:加载 JVM 运行所需的核心类
  2. 扩展类加载器:加载 Java 扩展类
  3. 系统类加载器:加载 Tomcat 自身的类
  4. Common 类加载器:加载 Tomcat 和所有 Web 应用共享的类
  5. Catalina 类加载器:加载 Tomcat 专用的内部类
  6. Shared 类加载器:加载所有 Web 应用共享的类
  7. WebApp 类加载器:为每个 Web 应用创建独立的类加载器,加载特定 Web 应用的类
  8. JSP 类加载器:为每个 JSP 页面创建的类加载器

为什么 Tomcat 需要自定义类加载器

1. 隔离性

问题:在一个 Java 应用服务器中可能同时运行多个 Web 应用,这些应用可能使用不同版本的相同库。

解决方案:Tomcat 为每个 Web 应用创建单独的 WebApp 类加载器,实现了类加载的隔离。这使得不同的 Web 应用可以使用同一个类的不同版本,而不会相互干扰。

2. 热部署/热加载

问题:在不重启整个服务器的情况下更新或重新部署单个 Web 应用。

解决方案:通过替换特定应用的 WebApp 类加载器,Tomcat 可以重新加载单个应用而不影响其他应用。当检测到 Web 应用的更新时,Tomcat 会丢弃原有的 WebApp 类加载器及其加载的所有类,创建一个新的类加载器重新加载应用,实现热部署。

3. 资源回收

问题:标准的 Java 类加载器可能导致内存泄漏,因为加载的类在类加载器存在的情况下无法被垃圾回收。

解决方案:当停止或重新部署 Web 应用时,Tomcat 可以丢弃相应的 WebApp 类加载器,使其加载的类和对象可以被垃圾回收,从而有效释放资源。

4. Web 应用间的依赖管理

问题:一些库应该在 Web 应用间共享,而另一些应该保持隔离。

解决方案:Tomcat 使用 Common 和 Shared 类加载器来加载共享的库,同时使用 WebApp 类加载器来加载应用特定的库,实现了灵活的依赖管理。

5. 类加载顺序的控制

问题:Java 默认遵循双亲委派模型,但这种模型在某些 Web 服务场景下不够灵活。

解决方案:Tomcat 的类加载器可以在特定情况下打破双亲委派模型,允许 Web 应用优先加载自己的类,而不是委托给父类加载器。这对于处理 Servlet API 等情况非常重要,Web 应用可能需要使用特定版本的 API,而不是服务器提供的版本。

类加载器委派模型的修改

Tomcat 修改了标准的双亲委派模型:

  1. 对于 Java SE 核心类(如 java.lang.*),仍然遵循双亲委派模型,由 Bootstrap 类加载器加载
  2. 对于 Web 应用自身的类,WebApp 类加载器会首先尝试加载,而不是立即委托给父类加载器
  3. 只有当 WebApp 类加载器找不到请求的类时,才会委托给父类加载器

这种修改使得 Tomcat 可以实现更灵活的类加载策略,满足 Web 应用服务器的特殊需求。

实际应用示例

假设有两个 Web 应用 A 和 B,它们使用不同版本的同一个库:

  • 应用 A 使用 log4j 1.2.17
  • 应用 B 使用 log4j 2.14.1

在标准 Java 类加载机制下,这两个版本会冲突。但在 Tomcat 中:

  1. 为应用 A 创建的 WebApp 类加载器加载 log4j 1.2.17
  2. 为应用 B 创建的 WebApp 类加载器加载 log4j 2.14.1

这样两个应用可以同时运行而不会相互干扰。

性能考虑

Tomcat 的类加载器架构虽然增加了复杂性,但实际上可以提高性能:

  1. 加载时间优化:只有在需要时才加载类,减少启动时间
  2. 内存优化:不同 Web 应用可以共享常用库的单一实例
  3. 资源释放:停止应用时可以释放其占用的类加载资源

结论

Tomcat 使用自定义类加载器是一种精心设计的解决方案,用于应对 Java Web 服务器环境中的特殊挑战。这种设计模式使 Tomcat 能够提供隔离性、热部署、资源管理和灵活性,同时仍然保持与 Java 类加载机制的兼容性。通过理解这种机制,开发人员可以更好地处理类加载相关的问题,并有效利用 Tomcat 提供的功能。